package com.nulldev.util.geolocation.ipr;

import java.io.File;
import java.net.InetAddress;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import com.nulldev.util.date.ExtendedTime;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.zipAPI.ZIPFile;
import com.nulldev.util.lang.csv.CSV;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.math.lNumber;
import com.nulldev.util.net.IPUtil;
import com.nulldev.util.promise.Promiser;
import com.nulldev.util.promise.noresult.NoResultPromise;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.nget.nGet;
import com.nulldev.util.uri.nget.nGetEvent;

/**
 * LICENSE FOR GeoLite <br>
 * This product includes GeoLite data created by MaxMind, available from <br>
 * <a href="http://www.maxmind.com">http://www.maxmind.com</a>. <br>
 * This product will be discontinued from 2019, and GeoDB will then be sourced from our own servers. <br>
 * We will also then support GeoDBv2.
 */
public class GeoDBHandler {

	private static boolean __has_DB = FileUtil.safeCreate(FileUtil.HOME_DIRECTORY + File.separator + "_geoDB.db").exists();
	/*
	 * LICENSE FOR GeoLite <br>
	 * This product includes GeoLite data created by MaxMind, available from <br>
	 * <a href="http://www.maxmind.com">http://www.maxmind.com</a>. <br>
	 * This product will be discontinued from 2019, and GeoDB will be sourced from our own servers. <br>
	 * We will also then support GeoDBv2.
	 */
	
	public static final URL __DOWNLOAD_LITE_DB_CSV = URIUtil.safeCreate("http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip");
	
	private static final CSV _db_csv = new CSV();
	private static LinkedList<GeoDBEntry> _entries = new LinkedList<GeoDBEntry>();
	
	static {
		if (!__has_DB) {
			try {
				final nGet ng = new nGet(__DOWNLOAD_LITE_DB_CSV);
				ng.setDirectory(FileUtil.safeCreate(FileUtil.HOME_DIRECTORY));
				ng.overrideName("_geoDB.db.zip");
				ng.disableOverridePrompt(true);
				ng.registerEvent(new nGetEvent() {
					
					@Override
					public void onStart(long length) {
						System.out.println("[GeoDB] Downloading database...");
					}
					
					@Override
					public void onFinish() {
						System.out.println("[GeoDB] Downloading database...FINISHED");
						
					}
					
					@Override
					public void onError() {
						System.out.println("[GeoDB] Downloading database...FAILED");
					}
					
					@Override
					public void onDownload(long bytes, double percent) {
						System.out.println("[GeoDB] Downloading database..." + FileUtil.convertToStringRepresentation(bytes));
					}
				});
				ng.download();
				ng.waitTillFinished();

				final File f0 = FileUtil.safeCreate(FileUtil.HOME_DIRECTORY + File.separator + "_geoDB.db.zip");
				final File f1 = FileUtil.safeCreate(FileUtil.HOME_DIRECTORY + File.separator + "_geoDB.db");
				final ZIPFile zpf = new ZIPFile(f0);
				zpf.decompress(FileUtil.safeCreate(FileUtil.HOME_DIRECTORY));
				FileUtil.safeCreate(FileUtil.HOME_DIRECTORY + File.separator + "GeoIPCountryWhois.csv").renameTo(f1);
				f0.delete();
				__has_DB = true;
			}catch (Exception e) {
				throw new RuntimeException(e);
			}
		}else {
			try {
				final File f1 = FileUtil.safeCreate(FileUtil.HOME_DIRECTORY + File.separator + "_geoDB.db");
				_db_csv.read(f1);
				//final lNumber it = lNumber.nil();
				final LinkedList<GeoDBEntry> _entries0 = new LinkedList<GeoDBEntry>();
				for (final List<String> valueFrom : _db_csv.clone()) {
					final GeoDBEntry gdbe = new GeoDBEntry(
							InetAddress.getByName(valueFrom.get(0)),
							InetAddress.getByName(valueFrom.get(1)), 
							MathUtil.safeParseL(valueFrom.get(2), -1), 
							MathUtil.safeParseL(valueFrom.get(3), -1), valueFrom.get(4), valueFrom.get(5));;
					_entries0.add(gdbe);
					//it.addSelf(1);
					//System.out.println(it);
				}
				_entries = _entries0;
			}catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	
	public static int entries() {
		return _db_csv.entries();
	}
	
	public static class GeoDBEntry {
		private final InetAddress from;
		private final InetAddress to;
		private final long value0, value1;
		private final String country_code, country;
		
		public GeoDBEntry(final InetAddress from, final InetAddress to, 
				final long value0, final long value1, final String cc, final String country) {
			this.from = from;
			this.to = to;
			this.value0 = value0;
			this.value1 = value1;
			this.country_code = cc;
			this.country = country;
		}
		
		public boolean isInRange(final InetAddress addr) {
			return IPUtil.inRange(addr, this.from, this.to);
		}
		
		public InetAddress from() {
			return this.from;
		}
		
		public InetAddress to() {
			return this.to;
		}
		
		public long value0() {
			return this.value0;
		}
		
		public long value1() {
			return this.value1;
		}
		
		public String countryCode() {
			return this.country_code;
		}
		
		public String country() {
			return this.country;
		}
		
		@Override
		public String toString() {
			return "GeoDBEntry[from=" + this.from.getHostAddress() + ",to=" + this.to.getHostAddress() + ",cc="
					+ this.country_code + ",country=" + this.country + "]";
		}
	}
	
	@Deprecated
	public static GeoDBEntry parse(final int position) throws Exception {
		final LinkedList<String> valueFrom = _db_csv.read(position);
		if (valueFrom.size() == 0) {
			return null;
		}
		return new GeoDBEntry(
				InetAddress.getByName(valueFrom.get(0)),
				InetAddress.getByName(valueFrom.get(1)), 
				MathUtil.safeParseL(valueFrom.get(2), -1), 
				MathUtil.safeParseL(valueFrom.get(3), -1), valueFrom.get(4), valueFrom.get(5));
	}
	
	public static GeoDBEntry read(final int position) {
		if (position < 0 || position > _entries.size() - 1) {
			return null;
		}
		return _entries.get(position);
	}
	
	public static LinkedList<GeoDBEntry> cloneList() {
		return (LinkedList<GeoDBEntry>) _entries.clone();
	}
}
